/***************************************************************************************
* Copyright (c) 2014-2022 Zihao Yu, Nanjing University
*
* NEMU is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/

#include <dlfcn.h>
#include "diff_test.h"

#ifdef DIFFTEST
typedef struct {
	uint64_t gpr[32];
	uint64_t pc;
} CPU_state;
typedef struct {
    int state;
    uint64_t halt_pc;
    uint32_t halt_ret;
} NPCState;
enum { NPC_RUNNING, NPC_STOP, NPC_END, NPC_ABORT, NPC_QUIT };
extern uint8_t pc_reg[0x80000000];
extern void get_cpu_reg();
extern void isa_reg_display();
extern char *regs[] ;
extern CPU_state cpu;
extern NPCState npc_state;
enum { DIFFTEST_TO_DUT, DIFFTEST_TO_REF };
static bool is_skip_ref = false;
static int skip_dut_nr_inst = 0;
void (*ref_difftest_memcpy)(uint64_t addr, void *buf, size_t n, bool direction) = NULL;
void (*ref_difftest_regcpy)(void *dut, bool direction) = NULL;
void (*ref_difftest_exec)(uint64_t n) = NULL;
void (*ref_difftest_raise_intr)(uint64_t NO) = NULL;
void (*ref_difftest_test)() = NULL;

void difftest_skip_ref() {
  is_skip_ref = true;
  skip_dut_nr_inst = 0;
}
void difftest_skip_dut(int nr_ref, int nr_dut) {
  skip_dut_nr_inst += nr_dut;

  while (nr_ref -- > 0) {
    ref_difftest_exec(1);
  }
}

void init_difftest(char *ref_so_file, long img_size, int port) {

	assert(ref_so_file != NULL);
	void *handle;
	handle = dlopen(ref_so_file, RTLD_LAZY);
	assert(handle);

	ref_difftest_memcpy =  (void (*)(uint64_t addr, void *buf, unsigned long n, bool direction))dlsym(handle, "difftest_memcpy");
	assert(ref_difftest_memcpy);

	ref_difftest_regcpy = (void (*)(void *dut, bool direction))dlsym(handle, "difftest_regcpy");
	assert(ref_difftest_regcpy);

	ref_difftest_exec = (void (*)(uint64_t n))dlsym(handle, "difftest_exec");
	assert(ref_difftest_exec);

	ref_difftest_raise_intr = (void (*)(uint64_t NO) )dlsym(handle, "difftest_raise_intr");
	assert(ref_difftest_raise_intr);

	void (*ref_difftest_init)(int) = (void (*)(int)) dlsym(handle, "difftest_init");
	assert(ref_difftest_init);
	ref_difftest_init(port);
	ref_difftest_memcpy(0x80000000, &pc_reg, img_size, DIFFTEST_TO_REF);
	get_cpu_reg();
	ref_difftest_regcpy(&cpu, DIFFTEST_TO_REF);
}

static inline bool difftest_check_reg(const char *name, word_t pc, word_t ref, word_t dut) {
	if (ref != dut) {
		printf("%s is different after executing instruction at pc = " "0x%lx"
			", right = " "0x%lx" ", wrong = " "0x%lx" ", diff = " "0x%lx\n",
			name, pc, ref, dut, ref ^ dut);
		return false;
	}
	return true;
}
bool isa_difftest_checkregs(CPU_state *ref_r, word_t pc) {
	for (size_t i = 0; i < 32; i++)
	{
		if(!difftest_check_reg(regs[i],cpu.pc,ref_r->gpr[i],cpu.gpr[i])){
			return false;
		}
	}
	if(!difftest_check_reg("pc",cpu.pc,ref_r->pc,cpu.pc)){
		return false;
	}
	
	return true;
}

static void checkregs(CPU_state *ref, word_t pc) {
    extern int ebreak;
	if (!isa_difftest_checkregs(ref, pc)) {
		npc_state.state = NPC_ABORT;
		// npc_state.halt_pc = pc;
		// ebreak=1;
		isa_reg_display();
	}
}
void difftest_step(word_t pc, word_t npc) {
	CPU_state ref_r;
	if (is_skip_ref) {
	// to skip the checking of an instruction, just copy the reg state to reference design
		ref_difftest_regcpy(&cpu, DIFFTEST_TO_REF);
		
		
		is_skip_ref = false;
		return;
	}
	ref_difftest_exec(1);
		
	ref_difftest_regcpy(&ref_r, DIFFTEST_TO_DUT);

	checkregs(&ref_r, pc);
}
void difftest_initr(uint64_t NO){
	ref_difftest_raise_intr(NO);
}
#else
void init_difftest(char *ref_so_file, long img_size, int port) { }
#endif
